package com.qualityevaluationcloudoauth2.config;

import com.qualityevaluationcloudoauth2.service.OauthUserService;
import com.qualityevaluationcloudoauth2.utils.MyPasswordEncoder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.common.*;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.JdbcApprovalStore;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.code.JdbcAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JdbcTokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.Date;
import java.util.UUID;

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    // 用户认证
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    RedisConnectionFactory redisConnectionFactory;
    @Autowired
    private DataSource dataSource;
    @Autowired
    private OauthUserService oauthUserService;
//    @Bean
//    @Primary
//    @ConfigurationProperties(prefix = "spring.datasource")
//    public DataSource dataSource() {
//        // 配置数据源（注意，我使用的是 HikariCP 连接池），以上注解是指定数据源，否则会有冲突
//        return DataSourceBuilder.create().build();
//    }

    @Bean
    public TokenStore tokenStore() {
        // 基于 JDBC 实现，令牌保存到数据
//        return new RedisTokenStore();
        return new JdbcTokenStore(dataSource);
    }

    @Bean
    public TokenStore redisTokenStore() {

        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        //redis key 前缀
//        tokenStore.setPrefix(DEMO_RESOURCE_ID+"_");
        return tokenStore;
    }

    @Bean
    public ClientDetailsService jdbcClientDetails() {
        // 基于 JDBC 实现，需要事先在数据库配置客户端信息
        return new JdbcClientDetailsService(dataSource);
    }

    //授权信息保存策略
    @Bean
    public ApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    @Bean
    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }
 /*   @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 设置令牌

        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);
    }*/

    //OAuth2的主配置信息
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .approvalStore(approvalStore())
                .authenticationManager(authenticationManager)
                .userDetailsService(oauthUserService)
                .tokenServices(tokenServices())
//                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(redisTokenStore());

    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // 配置客户端 未配置客户端表，这里改用固定的
        clients.inMemory()
                .withClient("OAUTH_CLIENT_ID")
                .authorizedGrantTypes("authorization_code", "client_credentials", "refresh_token", "password", "implicit")
                .secret(new MyPasswordEncoder().encode("OAUTH_CLIENT_SECRET"))
                .scopes("SCOPES")
                .accessTokenValiditySeconds(60 * 60 * 24 * 7)
//                .accessTokenValiditySeconds(60 )
                .refreshTokenValiditySeconds(60 * 60 * 24 * 30);

        // 读取客户端配置
//        clients.withClientDetails(jdbcClientDetails());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
        //允许表单认证
        oauthServer.allowFormAuthenticationForClients().tokenKeyAccess("isAuthenticated()");
        oauthServer.checkTokenAccess("permitAll()");
        oauthServer.tokenKeyAccess("permitAll()");
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        MyTokenService tokenService = new MyTokenService();
        tokenService.setTokenStore(redisTokenStore());
        tokenService.setSupportRefreshToken(true);
        return tokenService;
    }

    class MyTokenService extends DefaultTokenServices {

        private TokenEnhancer accessTokenEnhancer;

        public MyTokenService() {
        }

        @Override
        public OAuth2AccessToken readAccessToken(String accessToken) {
            return super.readAccessToken(accessToken);
        }

        @Override
        public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {
            TokenStore tokenStore = redisTokenStore();
            OAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);
            OAuth2RefreshToken refreshToken = null;
            // 重写此处，当用户关联的token 存在时，删除原有令牌
            if (existingAccessToken != null) {
                tokenStore.removeAccessToken(existingAccessToken);
            }

            if (refreshToken == null) {
                refreshToken = this.createRefreshToken(authentication);
            }
            else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {
                ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;
                if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {
                    refreshToken = createRefreshToken(authentication);
                }
            }

            OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);
            tokenStore.storeAccessToken(accessToken, authentication);
            // In case it was modified
            refreshToken = accessToken.getRefreshToken();
            if (refreshToken != null) {
                tokenStore.storeRefreshToken(refreshToken, authentication);
            }
            return accessToken;
        }

        private OAuth2RefreshToken createRefreshToken(OAuth2Authentication authentication) {
            if (!this.isSupportRefreshToken(authentication.getOAuth2Request())) {
                return null;
            } else {
                int validitySeconds = this.getRefreshTokenValiditySeconds(authentication.getOAuth2Request());
                String value = UUID.randomUUID().toString();
                return (OAuth2RefreshToken)(validitySeconds > 0 ? new DefaultExpiringOAuth2RefreshToken(value, new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L)) : new DefaultOAuth2RefreshToken(value));
            }
        }

        private OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
            DefaultOAuth2AccessToken token = new DefaultOAuth2AccessToken(UUID.randomUUID().toString());
            int validitySeconds = this.getAccessTokenValiditySeconds(authentication.getOAuth2Request());
            if (validitySeconds > 0) {
                token.setExpiration(new Date(System.currentTimeMillis() + (long)validitySeconds * 1000L));
            }

            token.setRefreshToken(refreshToken);
            token.setScope(authentication.getOAuth2Request().getScope());
            return (OAuth2AccessToken)(this.accessTokenEnhancer != null ? this.accessTokenEnhancer.enhance(token, authentication) : token);
        }

        @Override
        public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)
                throws AuthenticationException {

            OAuth2AccessToken token = super.refreshAccessToken(refreshTokenValue, tokenRequest);
            return token;
        }
    }

}